package install

import (
	"fmt"
	"io"
	"io/ioutil"
	"kubeInstall/cmd"
	"kubeInstall/pkg/client/ssh"
	"kubeInstall/server/models/app"
	"kubeInstall/utils"
	"kubeInstall/utils/file"
	"os"
	"strconv"
	"strings"
)

const oneKBByte = 1024
const oneMBByte = 1024 * 1024

type AsyncHandler struct {
	ssh      *ssh.SSH
	MsgQueue chan []byte
	Done     chan bool
}

func NewAsyncHandler(opt *SShOptions) (*AsyncHandler, error) {
	sshClient := &ssh.SSH{
		User:     opt.User,
		Password: opt.Password,
		HostIp:   opt.HostIp,
		Port:     opt.Port,
		PkFile:   opt.PkFile,
	}
	err := sshClient.SshInit()

	if err != nil {
		utils.Error("[ssh][%s]Error create ssh session failed,%s", opt.HostIp, err)
		return nil, err
	}

	return &AsyncHandler{
		ssh:      sshClient,
		MsgQueue: utils.MsgQueue,
		Done:     utils.Done,
	}, nil
}

func toSizeFromInt(length int) (float64, string) {
	isMb := length/oneMBByte > 1
	value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", float64(length)/oneMBByte), 64)
	if isMb {
		return value, "MB"
	} else {
		value, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", float64(length)/oneKBByte), 64)
		return value, "KB"
	}
}

func (h *AsyncHandler) CopyFile(localFilePath, remoteFilePath string) {
	utils.Debug("[ssh][%s][scpCopy] start to copy file %s to %s", h.ssh.HostIp, localFilePath, remoteFilePath)
	srcFile, err := os.Open(localFilePath)
	defer func() {
		if r := recover(); r != nil {
			utils.Error("[ssh][%s][scpCopy] open file failed: %s", h.ssh.HostIp, err)
		}
	}()
	if err != nil {
		panic(1)
	}
	defer srcFile.Close()

	dstFile, err := h.ssh.SftpClient.Create(remoteFilePath)
	defer func() {
		if r := recover(); r != nil {
			utils.Error("[ssh][%s][scpCopy] create remote file %s failed: %s", h.ssh.HostIp, dstFile.Name(), err)
		}
	}()
	if err != nil {
		panic(1)
	}
	defer dstFile.Close()
	buf := make([]byte, 100*oneMBByte) //100mb
	total := 0
	unit := ""
	for {
		n, _ := srcFile.Read(buf)
		if n == 0 {
			break
		}
		length, _ := dstFile.Write(buf[0:n])
		isKb := length/oneMBByte < 1
		speed := 0
		if isKb {
			total += length
			unit = "KB"
			speed = length / oneKBByte
		} else {
			total += length
			unit = "MB"
			speed = length / oneMBByte
		}
		totalLength, totalUnit := toSizeFromInt(total)
		utils.Info("[ssh][%s]transfer total size is: %.2f%s ;speed is %d%s", h.ssh.HostIp, totalLength, totalUnit, speed, unit)
	}
}

func (h *AsyncHandler) CmdAsync(cmd string) ([]byte, error) {
	utils.Info("[ssh][%s]exec cmd is : %s", h.ssh.HostIp, cmd)
	session, err := h.ssh.NewSession()

	if err != nil {
		utils.Error("[ssh][%s]Error create ssh session failed: %s", h.ssh.HostIp, err)
		return nil, err
	}
	defer session.Close()
	stdout, err := session.StdoutPipe()
	if err != nil {
		utils.Error("[ssh][%s]Unable to request StdoutPipe(): %s", h.ssh.HostIp, err)
		return nil, err
	}

	if err := session.Run(cmd); err != nil {
		utils.Error("[ssh][%s]Unable to execute command, error is : %s", h.ssh.HostIp, err)
		return nil, err
	}

	out, err := ioutil.ReadAll(stdout)

	if err != nil {
		utils.Error("[ssh][%s]Read command output failed, error is : %s", h.ssh.HostIp, err)
		return nil, err
	}
	utils.Info("[ssh][%s] cmd result is : %s", h.ssh.HostIp, out)
	return out, err

	/*r := bufio.NewReader(stdout)

	for {
		line, _, err := r.ReadLine()
		if line == nil || err != nil {
			return nil, err
		} else {
			utils.Info("[ssh][%s] cmd result is : %s", h.ssh.HostIp, line)
		}
	}*/

}
func (h *AsyncHandler) ConfigClusterHost(ip, hostname string) {
	h.CmdAsync(cmd.SetHostsString(ip, hostname))
}

func (h *AsyncHandler) ConfigHostName(hostname string) {
	//配置hostname
	h.CmdAsync(cmd.SetHostNameString(hostname))
}

func (h *AsyncHandler) GetHostType() string {

	resp, err := h.ssh.CmdToString(cmd.CheckHostIsVm(), "")
	if err != nil {
		return "Unknown"
	}
	if strings.Contains(resp, "Virtual") {
		return "Virtual machine"
	}

	return "Bare metal"
}

func (h *AsyncHandler) ConfigOsBaseEnv() {

	//关闭selinux
	h.CmdAsync(cmd.StopSelinux)

	//关闭防火墙
	h.CmdAsync(cmd.StopServiceString("firewalld"))
	h.CmdAsync(cmd.DisableServiceString("firewalld"))

	//关闭swap
	h.CmdAsync(cmd.StopSwap)

	//配置systcl
	h.CmdAsync(cmd.TouchFileAppendString("/etc/sysctl.d/k8s.conf", "net.bridge.bridge-nf-call-ip6tables = 1"))
	h.CmdAsync(cmd.TouchFileAppendString("/etc/sysctl.d/k8s.conf", "net.bridge.bridge-nf-call-iptables = 1"))
	h.CmdAsync(cmd.FreshSysCtl)
}

func (h *AsyncHandler) InstallBasePkgs(localPkgPath string) {

	basePath := "/opt/data/pkg/"

	if !h.ssh.IsDirExist(basePath) {
		h.CmdAsync(cmd.MkDirString(basePath))
	}

	fileList, _ := file.GetFileFullPath(localPkgPath)
	for _, fileOne := range fileList {
		filename := strings.Split(fileOne, "\\")
		remotePath := "/opt/data/pkg/" + filename[len(filename)-1]
		h.CopyFile(fileOne, remotePath)
	}
	h.CmdAsync(cmd.InstallPkgString("/opt/data/pkg/*"))
	h.CmdAsync(cmd.EnableServiceString("kubelet"))
	h.CmdAsync(cmd.StartServiceString("kubelet"))
	h.CmdAsync(cmd.EnableServiceString("docker"))
	h.CmdAsync(cmd.StartServiceString("docker"))

}

func (h *AsyncHandler) InstallNFS(serverIp, serverPath string) {
	h.CmdAsync(cmd.EnableServiceString("rpcbind"))
	h.CmdAsync(cmd.StartServiceString("rpcbind"))
	//配置NFS client
	basePath := "/mnt/data"

	h.CmdAsync(cmd.MkDirString(basePath))

	h.CmdAsync(fmt.Sprintf("mount -t nfs %s:%s %s", serverIp, serverPath, basePath))
	h.CmdAsync(cmd.SetFstab(serverIp, serverPath))

	h.CmdAsync(cmd.EnableServiceString("nfs"))
	h.CmdAsync(cmd.StartServiceString("nfs"))

}

func (h *AsyncHandler) LoadDockerImages(dockerImagePath string) {
	basePath := "/opt/data/images/"
	if !h.ssh.IsDirExist(basePath) {
		h.CmdAsync(cmd.MkDirString(basePath))
	}

	//fileName := path.Base(localFilePath)
	//remotePath := "/opt/data/docker/" + fileName

	fileList, _ := file.GetFileFullPath(dockerImagePath)
	for _, fileOne := range fileList {
		filename := strings.Split(fileOne, "\\")
		remotePath := "/opt/data/images/" + filename[len(filename)-1]
		h.CopyFile(fileOne, remotePath)
		h.CmdAsync(cmd.LoadDockerImageString(remotePath))
	}
}

//安装master
func (h *AsyncHandler) InstallMaster0(r *app.KubeAdmConfig) (*app.KubeAdmJoinOpts, error) {

	templateData := app.GetKubeadmConfig(r)
	kubeadmCmd := "echo \"" + templateData + "\" > /root/kubeadm-config.yaml"
	h.CmdAsync(kubeadmCmd)

	utils.Debug("start to init kubeadm config file")
	result, err := h.CmdAsync("kubeadm init --config /root/kubeadm-config.yaml")

	if err != nil && err != io.EOF {
		utils.Error("init kubeadm config file failed %v", err)
		return nil, err
	}

	utils.Debug("start to config cluster shell env ")
	h.CmdAsync(cmd.ClusterInit)

	slice := strings.Split(string(result), "kubeadm join")

	joinOpts := decodeJoinCmd(slice[1])

	joinOpts.MasterIP = h.ssh.HostIp + ":6443"
	utils.Debug("generate cluster join cmd :%v", joinOpts)
	return joinOpts, nil
}

//安装node
func (h *AsyncHandler) JoinK8sCluster(opts *app.KubeAdmJoinOpts) {

	joinCmd := "kubeadm join " + opts.MasterIP + " --discovery-token-ca-cert-hash " +
		opts.DiscoveryTokenCaCertHash + " --token " + opts.Token
	h.CmdAsync(joinCmd)
	utils.Debug("start to run join cmd: ", joinCmd)
}

//安装master
func (h *AsyncHandler) JoinK8sMaster(opts *app.KubeAdmJoinOpts) {
	h.JoinK8sCluster(opts)
}

func (h *AsyncHandler) TagNodeLables(nodeName, labels string) error {
	//kubectl label nodes master1 isIngress=true
	cmdToRun := fmt.Sprintf("kubectl label nodes %s %s", nodeName, labels)
	h.CmdAsync(cmdToRun)
	return nil
}

func (h *AsyncHandler) InstallApps(app, yamlContent string) {

	basePath := "/opt/data/app/"
	if !h.ssh.IsDirExist(basePath) {
		h.CmdAsync(cmd.MkDirString(basePath))
	}

	appPath := fmt.Sprintf("/opt/data/app/%s.yaml", app)
	writeCNICmd := fmt.Sprintf(`echo '%s' > '%s'`, yamlContent, appPath)

	h.CmdAsync(writeCNICmd)

	h.CmdAsync(fmt.Sprintf("kubectl create -f %s", appPath))
}

func (h *AsyncHandler) GetDNSServerIp() string {

	result, err := h.ssh.CmdToString(cmd.GetDNSServerIp(), "")
	if err != nil {
		utils.Error("add cluster DNS cache failed! error is:  %v\n", err)
		return ""
	}

	return result
}

func decodeJoinCmd(cmd string) *app.KubeAdmJoinOpts {
	utils.Debug("[globals]decodeJoinCmd: %s", cmd)
	stringSlice := strings.Split(cmd, " ")

	opts := new(app.KubeAdmJoinOpts)
	for i, r := range stringSlice {
		switch r {
		case "--token":
			opts.Token = stringSlice[i+1]
		case "--discovery-token-ca-cert-hash":
			opts.DiscoveryTokenCaCertHash = stringSlice[i+1]

		}
	}

	return opts
}
